/*
 * ID3Renamer.java
 *
 * Created on 25 aprile 2007, 14.59
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package alecava;

import java.util.*;
import java.io.*;
/**
 *
 * @author alecava
 */
public class ID3FileNameHandler {
    
    /**prende un pattern per il nome del file del tipo ("titolo" - "artista"), estrae di conseguenza i tag dal nome del file
     * e li restituisce con un array di String. Gli operatori concessi sono: artista, titolo, album, traccia, genere, anno, commento.
     * <b>E' necessario che i campi nel nome del file siano separati da una carattere non alfanumerico</b>
     @return un array di String con i campi da scrivere nell'mp3, l'ordine è:
     <i>artista, titolo, album, commento, anno, genere, traccia</i>. I tag che non sono specificati nel pattern ma che sono già presenti nell'mp3
     saranno contenuti nell'array*/
    public String[] extractTagFromFileName(final String mask, AbstractCavaID3 mp3) throws SintassiErrataException{
        String nome = mp3.getFile().getName();
        //toglie l'estensione
        if (nome.endsWith(".mp3"))
            nome = nome.substring(0, nome.length() - 4);
        //strutture di appoggio
        HashMap<String, String> map = new HashMap<String, String>();
        List<String> tokens = new ArrayList<String>();
        List<String> sep = new ArrayList<String>();
        //inizializza lo StreamTokenizer
        StreamTokenizer stk = new StreamTokenizer(new StringReader(mask));
        stk.quoteChar('"');
        
        try {
            /*estrae le chiavi da utilizzare con il nome del file*/
            while (stk.nextToken() != stk.TT_EOF){
                if (stk.ttype == '"'){
                    //inserisco le chiavi
                    //System.out.println("aggiunta chiave: " + stk.sval);
                    tokens.add(stk.sval);
                } else {
                    //inserisco anche i separatori
                    //System.out.println("aggiunto separatore: " + (char)stk.ttype);
                    sep.add(String.valueOf((char) stk.ttype));
                }
            }
            /*controlla che non vengano usati token non previsti*/
            for (String s : tokens){
                if (!(s.equals("titolo") || s.equals("artista") || s.equals("album") || s.equals("commento")
                || s.equals("anno") || s.equals("genere") || s.equals("traccia")))
                    throw new SintassiErrataException("Operatore non previsto");       
            }
            /*contano la posizione in lettura dei token e dei separatori*/
            int indexT = 0, indexS = 0;
            //costruisce un nuovo StreamTokenizer
            stk = new StreamTokenizer(new StringReader(nome));
            /*imposta il comportamento*/
            stk.ordinaryChar('\'');
            stk.slashSlashComments(false);
            stk.slashStarComments(false);
            stk.parseNumbers();
            /*estrae i token dal nome del file*/
            while (stk.nextToken() != stk.TT_EOF){
                String key = tokens.get(indexT);
                switch (stk.ttype){
                    case StreamTokenizer.TT_WORD:
                        //System.out.println("aggiungo parola");
                        //inserisce le coppie chiave-valore nella map
                        map.put(key, (map.containsKey(key) ? map.get(key) + " " : "") + stk.sval);
                        break;
                    case StreamTokenizer.TT_NUMBER:
                        //System.out.println("aggiungo numero");
                        //inserisce le coppie chiave-valore nella map
                        map.put(key, (map.containsKey(key) ? map.get(key) + " " : "") + String.valueOf((int)stk.nval));
                        break;
                    default:
                        //se il separatore è previsto passa al token successivo
                        if (sep.size() > indexS && sep.get(indexS).equals(String.valueOf((char) stk.ttype))){
                            //System.out.println("uguale, incremento gli indici");
                            indexS++;
                            indexT++;
                        } else {
                            //System.out.println("diverso, aggiungo carattere speciale: " + (char) stk.ttype); 
                            //aggiunge anche il carattere particolare
                            map.put(key, (map.containsKey(key) ? map.get(key) : "") + (char) stk.ttype);
                        }
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (IndexOutOfBoundsException e){
            throw new SintassiErrataException("L'espressione non corrisponde al nome del file");
        }
        String[] arr = new String[7];
        /*se tutto è andato bene scrive nel file*/
        arr[0] = map.containsKey("artista") ? map.get("artista").trim() : mp3.getArtist();
        arr[1] = map.containsKey("titolo") ? map.get("titolo").trim() : mp3.getTitle();
        arr[2] = map.containsKey("album") ? map.get("album").trim() : mp3.getAlbum();
        arr[3] = map.containsKey("commento") ? map.get("commento").trim() : mp3.getComment();
        arr[4] = map.containsKey("anno") ? map.get("anno").trim() : mp3.getYear();
        arr[5] = map.containsKey("genere") ? map.get("genere").trim() : mp3.getGenre();
        arr[6] = map.containsKey("traccia") ? map.get("traccia").trim() : mp3.getTrack();
        //System.out.println("art " + artista+" tit "+ titolo+" alb "+ album+ " comm "+commento+" ann "+ anno+" gen "+ genere + " tra "+traccia);
        
        return arr;
    }
    
    /**rinomina gli mp3 nella Collection, dopo aver interpretato il pattern passato come primo argomento
     @see parseRenamePattern()*/
    public void renameFiles(final String espr, final Collection<AbstractCavaID3> files) throws SintassiErrataException{
        for (AbstractCavaID3 file : files){
            String nome = this.parseRenamePattern(espr, file);
            File nuovo = new File(file.getFile().getParent() + File.separatorChar + nome);
            //rinomina solo se non esista già e se il nome non è vuoto
            if (!nuovo.exists() && !nome.equals(".mp3")){
                file.getFile().renameTo(nuovo);
            }
        }
    }
    /**prende un pattern del tipo ?traccia! - &#60;artista&#62; - &#60;titolo&#62; e un oggetto di tipo CavaID3
     * 
     * all'interpretazione del pattern. E' possibile usare caratteri diversi da lettere per delimitare i campi ed usare
     *tag condizionali ( ?traccia! ) che significano che se la traccia non ha un valore significativo verrà omessa
     *e verrà omesso anche il separatore che la segue.
     *@return una stringa col nome del file specificato dal pattern
     */
    public String parseRenamePattern(final String espr, final AbstractCavaID3 mp3) throws SintassiErrataException {
        String nuovoNome = "";
        
        boolean aperto = false;
        boolean apertoCondizionale = false;
        boolean tagEsiste = true;
        boolean separatoreIsUltimo = false;
        
        Map<String, String> tab = new HashMap<String, String>();
        /*costruisce l'hashtable con i valori dei tag*/
        tab.put("artista", mp3.getArtist());
        tab.put("titolo", mp3.getTitle());
        tab.put("album", mp3.getAlbum());
        tab.put("commento", mp3.getComment());
        tab.put("anno", mp3.getYear());
        tab.put("traccia", mp3.getTrack());
        tab.put("genere", mp3.getGenre());
        /*fa il parsing dell'espressione*/
        StreamTokenizer stk = new StreamTokenizer(new StringReader(espr));
        /*imposta il comportamento*/
        stk.slashSlashComments(false);
        stk.slashStarComments(false);
        /****************************/
        try {
            while (stk.nextToken() != StreamTokenizer.TT_EOF){
                switch (stk.ttype){
                    case StreamTokenizer.TT_WORD:
                        String token = stk.sval;
                        String val = null;
                        /*prende il valore*/
                        if (aperto || apertoCondizionale){
                            val = tab.get(token);
                            if (val == null) throw new SintassiErrataException("Sintassi scorretta");
                            /*se è un numero di traccia mette lo zero*/
                            val = setZero(val, token);
                        }
                        /*se è un'operatore*/
                        if (aperto){
                            nuovoNome += " " + val + " ";
                            //!!!bisogna mettere un eventuale separatore
                            tagEsiste = true;
                            /*se è un operatore condizionale*/
                        } else if (apertoCondizionale) {
                            /*converte in numero*/
                            int valNum = 0;
                            try {
                                valNum = Integer.parseInt(val);
                            } catch (NumberFormatException e){
                                /*se non è un numero, o è una stringa vuota o
                                 l'espressione deve dare true*/
                                valNum = 1;
                            }
                            /*********************************/
                            /*controlla se il tag è effettivamente presente
                             o se è solo un campo vuoto (o zero se trackNumber)*/
                            if (val.equals("") || valNum == 0){
                                tagEsiste = false;
                                separatoreIsUltimo = true;
                            } else {
                                tagEsiste = true;
                                nuovoNome += " " + val + " ";
                                separatoreIsUltimo = false;
                            }
                            /*senò aggiunge il testo grezzo*/
                        } else {
                            nuovoNome += " " + token + " ";
                            //!!!bisogna mettere un eventuale separatore
                            tagEsiste = true;
                        }
                        break;
                    case '<':
                        aperto = true;
                        break;
                    case '>':
                        aperto = false;
                        break;
                    case '?':
                        apertoCondizionale = true;
                        break;
                    case '!':
                        apertoCondizionale = false;
                        break;
                    default:
                        //!!!!se non incontra un separatore subito dopo tralascia il primo che trova!!!
                        if (!tagEsiste)
                            tagEsiste = true;
                        else {
                            nuovoNome += (char) stk.ttype;
                        }
                }
            }
            /*rimuove un eventuale separatore inutile*/
            if (separatoreIsUltimo){
                try{
                    nuovoNome = nuovoNome.substring(0, nuovoNome.length() - 1);
                } catch (StringIndexOutOfBoundsException e){
                    throw new SintassiErrataException(e.getMessage());
                }
            }
            /*rimuove gli spazi inutili*/
            nuovoNome = nuovoNome.trim();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return nuovoNome + ".mp3";
    }
    /**dato un nome di file estrae il numero di traccia se possibile
     *@return una stringa col numero di traccia estratto o una stringa vuota se non è
     *stato possibile estrarre nulla.
     */
    public String extractTrack(String fileName){
        StreamTokenizer stk = new StreamTokenizer(new StringReader(fileName));
        stk.parseNumbers();
        //estrae tutti i numeri dal nome del file e ritorna quello opportuno
        //(nei cd ci possono essere al massimo 99 tracce)
        try {
            while (stk.nextToken() != stk.TT_EOF){
                if (stk.ttype == stk.TT_NUMBER){
                    int toRet = (int) stk.nval;
                    if (toRet > 0 && toRet < 100){
                        return String.valueOf(toRet);
                    } else {
                        /*a volte con album a diversi cd
                        alcuni usano <numerocd><traccia> quindi
                         elimino il primo numero*/
                        String num = String.valueOf(toRet);
                        if (num.length() == 3){
                            return num.substring(1, 3);  
                        }
                    }
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return "";
    }
    
    private String setZero(String x, String tipo){
        int t = 0;
        /*con i tag versione 2.x la traccia non è necessariamente un intero
         , può anche essere qualcosa tipo 1/10*/
        try{
            t = Integer.parseInt(x);
        } catch (NumberFormatException e){
            return x;
        }
        /*aggiunge uno zero davanti ad un numero di traccia < 10 così
                             i file vengono ordinati in modo corretto nei file managers*/
        if (tipo.equals("traccia") && t < 10 && !(x.charAt(0) == '0'))
            x = "0" + x;
        /**********************************************************/
        return x;
    }
    
}
